package com.xaicode.auth.controller;

import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.xaicode.auth.annotation.Log;
import com.xaicode.auth.domain.Result;
import com.xaicode.auth.entity.SysUser;
import com.xaicode.auth.enums.BusinessType;
import com.xaicode.auth.security.AuthConst;
import com.xaicode.auth.security.LoginService;
import com.xaicode.auth.security.LoginUtil;
import com.xaicode.auth.security.OnlineUserVo;
import com.xaicode.auth.security.domain.LoginBody;
import com.xaicode.auth.security.domain.LoginUser;
import com.xaicode.auth.security.domain.params.AuthKickoutParam;
import com.xaicode.auth.security.domain.params.LogoutForceParam;
import com.xaicode.auth.security.domain.query.OnlineUserQuery;
import com.xaicode.auth.security.domain.resp.CaptchaResp;
import com.xaicode.auth.security.domain.resp.CurrentUserInfoResp;
import com.xaicode.auth.service.ISysUserService;
import com.xaicode.auth.utils.RedisUtil;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.wf.captcha.ArithmeticCaptcha;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 登录认证
 */
@Slf4j
@RestController
@RequestMapping("/api/auth")
public class AuthController {

    @Autowired
    private LoginService loginService;
    @Autowired
    private ISysUserService sysUserService;
    @Autowired
    private RedisUtil redisUtil;


    /**
     * 获取验证码
     * <p>
     * 当前创建计算类验证码
     *
     * @return .
     */
    @SaIgnore
    @GetMapping("/captcha")
    public Result<?> captcha() {

        // 算术类型
        ArithmeticCaptcha captcha = new ArithmeticCaptcha(150, 38);
        // 几位数运算，设置为两位数计算
        captcha.setLen(2);
        // 例：获取运算的公式：3+2=?
        captcha.getArithmeticString();
        // 例：获取运算的结果：5
        captcha.text();

        // 保存验证码到redis
        String uuid = IdUtil.fastSimpleUUID();
        redisUtil.setEx(AuthConst.CAPTCHA_REDIS_KEY + uuid,
                captcha.text(),
                AuthConst.CAPTCHA_EXPIRATION, TimeUnit.SECONDS);
        log.info("[Captcha] uuid: {}, captcha: {}", uuid, captcha.text());

        CaptchaResp captchaResp = new CaptchaResp();
        // uuid 用于后续校验时定位请求
        captchaResp.setUuid(uuid);
        // img 页面图片
        captchaResp.setImg(captcha.toBase64());

        return Result.ok(captchaResp);
    }

    /**
     * 登录接口
     *
     * @param loginBody .
     * @return .
     */
    @Log(title = "登录", businessType = BusinessType.LOGIN,
            isSaveResponseData = false, excludeParamNames = {"password"})
    @SaIgnore
    @PostMapping("/login")
    public Result<?> login(@RequestBody LoginBody loginBody) {
        try {
            String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(),
                    loginBody.getUuid(), loginBody.getVerifyCode());
            return Result.ok(token);
        } catch (Exception e) {
            log.error("[Auth][Login] 登录失败：{}", e.getMessage(), e);
            return Result.fail(e.getMessage());
        }
    }

    /**
     * 获取登陆用户信息
     *
     * @return .
     */
    @SaIgnore
    @PostMapping("/info")
    public Result<?> info() {
        try {
            LoginUser user = LoginUtil.getLoginUser();
            List<String> permissions = StpUtil.getPermissionList();
            List<String> roles = StpUtil.getRoleList();

            CurrentUserInfoResp infoResp = new CurrentUserInfoResp();
            infoResp.setUser(user);
            infoResp.setPermissions(permissions);
            infoResp.setRoles(roles);

            // 更新用户活跃时间
            String userId = StpUtil.getLoginIdAsString();
            LambdaUpdateWrapper<SysUser> updateWrapper = new LambdaUpdateWrapper<SysUser>()
                    .eq(SysUser::getId, userId)
                    .set(SysUser::getLastActiveTime, LocalDateTime.now());
            sysUserService.update(updateWrapper);

            return Result.ok(infoResp);
        } catch (NotLoginException e1) {
            log.error("[Auth][Info] 未登录：{}", e1.getMessage());
            // 由全局认证异常类处理
            throw e1;
        } catch (Exception e2) {
            log.error("[Auth][Info] 获取用户信息失败：{}", e2.getMessage());
            return Result.fail();
        }
    }

    /**
     * 退出登录
     *
     * @return .
     */
    @Log(title = "登出", businessType = BusinessType.LOGOUT)
    @SaIgnore
    @PostMapping("/logout")
    public Result<?> logout() {
        StpUtil.logout();
        return Result.ok();
    }


    /**
     * 在线用户列表
     * <p>
     * 内部业务，不做分页
     */
    @Log(title = "查看在线用户列表", businessType = BusinessType.QUERY, isSaveResponseData = false)
    @SaCheckPermission("monitor:online:list")
    @PostMapping("/online")
    public Result<?> online(@RequestBody OnlineUserQuery query) {
        String nickname = query.getNickname();

        // 获取所有已登录的会话id(如果系统使用的用户比较多，此处会有内存压力，此处限制最多99条数据)
        List<String> sessionIdList = StpUtil.searchSessionId("", 0, 99, true);

        List<OnlineUserVo> list = new ArrayList<>();
        for (String sessionId : sessionIdList) {

            // 根据会话id，查询对应的 SaSession 对象，此处一个 SaSession 对象即代表一个登录的账号
            SaSession session = StpUtil.getSessionBySessionId(sessionId);
            LoginUser authUser = LoginUtil.getLoginUserBySaSession(session);

            // 过滤查询
            if (StrUtil.isNotBlank(nickname) && !authUser.getNickname().contains(nickname)) {
                continue;
            }

            OnlineUserVo onlineUserVo = new OnlineUserVo();
            onlineUserVo.setId(authUser.getUserId());
            onlineUserVo.setUsername(authUser.getUsername());
            onlineUserVo.setNickname(authUser.getNickname());
            onlineUserVo.setDeptName(authUser.getDeptName());
            onlineUserVo.setLoginIp(authUser.getLoginIp());
            onlineUserVo.setLoginRegion(authUser.getLoginRegion());
            onlineUserVo.setLoginTime(authUser.getLoginTime());
            onlineUserVo.setBrowser(authUser.getBrowser());
            onlineUserVo.setOs(authUser.getOs());
            list.add(onlineUserVo);
        }

        return Result.ok(list);
    }

    @Log(title = "踢人下线", businessType = BusinessType.KICKOUT)
    @SaCheckPermission("monitor:online:kickout")
    @PostMapping("/online/kickout")
    public Result<?> kickout(@RequestBody AuthKickoutParam param) {
        String userId = param.getUserId();
        StpUtil.kickout(userId);
        return Result.ok();
    }

    /**
     * 注销指定用户
     *
     * @param param .
     * @return .
     */
    @Log(title = "强制注销", businessType = BusinessType.FORCE_LOGOUT)
    @SaCheckPermission("monitor:online:logout")
    @PostMapping({"/online/logout", "/logout/force"})
    public Result<?> logoutForce(@RequestBody LogoutForceParam param) {
        String userId = param.getUserId();
        StpUtil.logout(userId);
        return Result.ok();
    }

}
